<div id="content_views" class="htmledit_views">
                    <div id="js_content"> 
 <p style="text-align: left">前端是一个神奇的存在，只需你写出代码，下一秒见证奇迹的时刻就会到来，今天用canvas实现一个万有引力的特效，对，你没听错，就是这么的amazing~</p>
 <p style="text-align: left">先看最终效果<sup>[2]</sup>：<br></p>
 <p style="text-align: center"><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi95MHJzSU5QcmxaelphSlJXQjZpYjU3SzhMUGtwbzZxejBQY3prZTBqYjBSaWJlRzFVTmlhYkp0RXh5VVhUWExOS0NSUVZNdHhrS2s0aWJTcjJzeWliSXBTdExBLzY0MA?x-oss-process=image/format,png"></p>
 <p style="text-align: left">先说明一下，此效果启发于第二本参考书。</p>
 <p style="text-align: left">中心红色球是太阳，水蓝色小球是地球。可以看出地球的运行轨道是椭圆，验证了开普勒三定律的第一条。在近日点运行速度比较快，远日点比较慢，符合常识。</p>
 <p style="text-align: left">你猜测我写这个效果用了多长时间？</p>
 <p style="text-align: left">不到半个小时吧，其实掌握原理后实现起来还是比较快的。</p>
 <p style="text-align: left">原理相对来说还是简单的，前提是要使用向量工具。</p>
 <h3><a name="t0"></a>1. 用向量来思考问题</h3>
 <p style="text-align: left">本系列的前几篇文章，都是使用 x、y 轴分量来计算小球的物理矢量。</p>
 <pre class="has" name="code"><code class="language-javascript hljs">fx = f * <span class="hljs-built_in">Math</span>.cos(angle)fy = f * <span class="hljs-built_in">Math</span>.sin(angle)ax = fx / ball.may = fy / ball.mvx += axvy += ayball.x += vxball.y += vy
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">上述代码中，f 是力，m 是质量，a 是加速度，v 是速度。而带后缀的是相应矢量的分量，比如 fx 是力矢量 f 的横轴分量。</p>
 <p style="text-align: left">上述运算，本质上还是使用小学的加减法，没有真正用到矢量或者说向量。</p>
 <p style="text-align: left">假设我们向量&nbsp;<strong>p</strong>&nbsp;表示小球当前位置，向量&nbsp;<strong>v</strong>&nbsp;表示当前速度，那么下一帧的小球的位置为下图中的粉色向量。不用再分别计算 x 轴 和 y 轴的分量。</p>
 <p style="text-align: left"><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy95MHJzSU5QcmxaelphSlJXQjZpYjU3SzhMUGtwbzZxejA3TjBVNzdUOWhUWXJkdDZRc0FXd0xzR2ZzVVVjdE9STzZlNEw2b2lhS3Y3b0VSd0Q3dnprSmp3LzY0MA?x-oss-process=image/format,png" title="null"></p>
 <p style="text-align: left">为了表示向量这一数据类型，我们简单地封装一个 Vector 类：</p>
 <pre class="has" name="code"><code class="language-javascript hljs"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Vector</span> </span>{  <span class="hljs-keyword">constructor</span>(x, y) {    <span class="hljs-built_in">this</span>.x = x    <span class="hljs-built_in">this</span>.y = y  }  scale(n) {    <span class="hljs-built_in">this</span>.x *= n    <span class="hljs-built_in">this</span>.y *= n    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>  }  mag() {    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.sqrt(<span class="hljs-built_in">this</span>.x * <span class="hljs-built_in">this</span>.x + <span class="hljs-built_in">this</span>.y * <span class="hljs-built_in">this</span>.y);  }  normalize() {    <span class="hljs-keyword">var</span> m = <span class="hljs-built_in">this</span>.mag()    <span class="hljs-keyword">if</span> (m != <span class="hljs-number">0</span> &amp;&amp; m != <span class="hljs-number">1</span>) {      <span class="hljs-built_in">this</span>.scale(<span class="hljs-number">1</span> / m)    }    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>  }  add(other) {    <span class="hljs-built_in">this</span>.x += other.x    <span class="hljs-built_in">this</span>.y += other.y    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>  }  sub(other) {    <span class="hljs-built_in">this</span>.x -= other.x    <span class="hljs-built_in">this</span>.y -= other.y    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>  }  <span class="hljs-keyword">static</span> add(one, other) {    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Vector(one.x + other.x, one.y + other.y)  }  <span class="hljs-keyword">static</span> sub(one, other) {    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Vector(one.x - other.x, one.y - other.y)  }  <span class="hljs-keyword">static</span> dist(one, other) {    <span class="hljs-keyword">var</span> dx = one.x - other.x    <span class="hljs-keyword">var</span> dy = one.y - other.y    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.sqrt(dx * dx + dy * dy)  }}
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">这些向量操作都很简单，比如 add 表示当前向量加上一个新向量，mag 用来求取向量大小，scale 用来放缩向量的大小，normalize 用于计算单位向量等等。虽然底层仍是 x、y 分量用于计算。但是我们思维的最小单位是向量，维度高级一些。</p>
 <p style="text-align: left">比如之前的代码用向量的形式表示：</p>
 <pre class="has" name="code"><code class="language-javascript hljs">a = f.scale(<span class="hljs-number">1</span> / ball.m)v.add(a)ball.p.add(v)
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">有了向量工具，处理万有引力公式起来就很 easy 了。</p>
 <h3><a name="t1"></a>2. 动画原理</h3>
 <p style="text-align: left">这部分，我们来大体看下开篇动画的原理，看看是否真的简单。</p>
 <p style="text-align: left">地球人都知道牛顿的<strong>万有引力定律</strong>这一伟大发现：</p>
 <blockquote>
  <p>任意两个质点有通过连心线方向上的力相互吸引。该引力大小与它们质量的乘积成正比与它们距离的平方成反比，与两物体的化学组成和其间介质种类无关。</p>
 </blockquote>
 <p style="text-align: left"><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy95MHJzSU5QcmxaelphSlJXQjZpYjU3SzhMUGtwbzZxejBIV3RaRzBaaFFzWTFGTzg1Wk9zMW1ERko3YjdGaWNIUFJQOUc3TWlhYVg4Z1JnMEVpYjhKR2hGUWcvNjQw?x-oss-process=image/format,png" title="null"></p>
 <p style="text-align: left">其中 G 是万有引力常数，动画里我们简化为 1 就行。</p>
 <p style="text-align: left">把公式写成代码：</p>
 <pre class="has" name="code"><code class="language-javascript hljs"><span class="hljs-keyword">var</span> dist = Vector.dist(sun.p, earth.p)<span class="hljs-keyword">var</span> f = sun.m * earth.m / (dist * dist)
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">而这个 f 目前仍是标量，只有大小，没有方向。地球受到的万有引力，方向应该是朝向太阳的，因此完整代码是：</p>
 <pre class="has" name="code"><code class="language-javascript hljs"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">gravity</span> (<span class="hljs-params">sun, earth</span>) </span>{  <span class="hljs-keyword">var</span> dist = Vector.dist(sun.p, earth.p)  <span class="hljs-keyword">var</span> f = sun.m * earth.m / (dist * dist)  <span class="hljs-keyword">var</span> vec = Vector.sub(sun.p, earth.p)  <span class="hljs-keyword">return</span> vec.normalize().scale(f)}
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">之所以封装成函数是因为地日之间的万有引力是时刻发生变化的。其中向量 vec 方向是朝向太阳的，对其取单位向量，并放缩大小为 f。这样，最终返回的结果是引力向量。</p>
 <p style="text-align: left">因此地球的运动核心代码很容易写出来：</p>
 <pre class="has" name="code"><code class="language-javascript hljs"><span class="hljs-keyword">var</span> g = gravity(sun, earth)<span class="hljs-keyword">var</span> a = g.scale(<span class="hljs-number">1</span> / earth.m)v.add(a)earth.p.add(v)
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">核心原理就是这些，剩下就是实现了。</p>
 <h3><a name="t2"></a>3. 实现</h3>
 <p style="text-align: left">太阳和地球采用的都是小球模型：</p>
 <pre class="has" name="code"><code class="language-javascript hljs"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Ball</span></span>{  <span class="hljs-keyword">constructor</span>() {    <span class="hljs-built_in">this</span>.p = <span class="hljs-keyword">new</span> Vector(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>)    <span class="hljs-built_in">this</span>.m = <span class="hljs-number">0</span>    <span class="hljs-built_in">this</span>.radius = <span class="hljs-number">20</span>    <span class="hljs-built_in">this</span>.color = <span class="hljs-string">'white'</span>  }  draw(context) {    context.save()    context.translate(<span class="hljs-built_in">this</span>.p.x, <span class="hljs-built_in">this</span>.p.y)    context.beginPath()    context.arc(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-built_in">this</span>.radius, <span class="hljs-number">0</span>, <span class="hljs-number">2</span> * <span class="hljs-built_in">Math</span>.PI)    context.fillStyle = <span class="hljs-built_in">this</span>.color    context.fill()    context.restore()  }}
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">这段代码不是本系列第一次出现了，唯一不同的是原先的 this.x 和 this.y 改用向量 this.p 替换了。</p>
 <p style="text-align: left">初始化太阳和地球：</p>
 <pre class="has" name="code"><code class="language-javascript hljs"><ol class="hljs-ln" style="width:1229px"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-keyword">var</span> sun = <span class="hljs-keyword">new</span> Ball()sun.radius = <span class="hljs-number">30</span>sun.color = <span class="hljs-string">'red'</span>sun.p = <span class="hljs-keyword">new</span> Vector(canvas.width / <span class="hljs-number">2</span>, canvas.height / <span class="hljs-number">2</span>)sun.m = <span class="hljs-number">1000</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-keyword">var</span> earth = <span class="hljs-keyword">new</span> Ball()earth.radius = <span class="hljs-number">3</span>earth.color = <span class="hljs-string">'aqua'</span>earth.p = <span class="hljs-keyword">new</span> Vector(canvas.width / <span class="hljs-number">2</span> + <span class="hljs-number">100</span>, canvas.height / <span class="hljs-number">2</span> + <span class="hljs-number">80</span>)earth.m = <span class="hljs-number">1</span></div></div></li></ol></code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">其中太阳和地球的质量比是 1000: 1。地球的位置比较随便选取了一个位置。</p>
 <p style="text-align: left">接下来就是原理的核心代码，其中我们假设太阳不动的。</p>
 <pre class="has" name="code"><code class="language-javascript hljs">;(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawFrame</span>(<span class="hljs-params"></span>) </span>{  <span class="hljs-built_in">window</span>.requestAnimationFrame(drawFrame)  context.clearRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height)  <span class="hljs-keyword">var</span> g = gravity(sun, earth)  <span class="hljs-keyword">var</span> a = g.scale(<span class="hljs-number">1</span> / earth.m)  v.add(a)  earth.p.add(v)  earth.draw(context)  sun.draw(context)})()
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">这里有一个至关重要的点，是地球需要有个初始速度 v 。</p>
 <p style="text-align: left">不同大小和方向的速度，地球的运行轨道会有所不同。假如初始速度为 0 向量的话，根据万有引力，地球将会撞向太阳：</p>
 <p style="text-align: left"><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi95MHJzSU5QcmxaelphSlJXQjZpYjU3SzhMUGtwbzZxejA0N2ljU2liaWJhRkNpYTdxdmJFaG9SMkMyR3pNOXd2MzJGZkVoNGZYVTJpY2liOVRrcHJtYVNkbUFBTUEvNjQw?x-oss-process=image/format,png" title="null"></p>
 <p style="text-align: left">当然这里地球撞上太阳后，没有销毁，但是地球迅速远离太阳。因为速度比较快，万有引力再也拉不回来了。这其实就是三体小说中的“引力弹弓”。</p>
 <p style="text-align: left">我们让太阳尺寸小点，速度选择为：</p>
 <pre class="has" name="code"><code class="language-javascript hljs"><span class="hljs-keyword">var</span> v = <span class="hljs-keyword">new</span> Vector(<span class="hljs-number">-0.3</span>, <span class="hljs-number">1</span>)
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">引力弹弓效果将更好些：</p>
 <p style="text-align: left"><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi95MHJzSU5QcmxaelphSlJXQjZpYjU3SzhMUGtwbzZxejBXaWFCVEZXaWNRSTkxaWNKb2lhT1h5dnVkRVZpYkRUWGlib1g5V244YzdZT2htMlA3SkNpYWlhY2lhbWljRktnLzY0MA?x-oss-process=image/format,png" title="null"></p>
 <p style="text-align: left">假如速度选择为：</p>
 <pre class="has" name="code"><code class="language-javascript hljs"><span class="hljs-keyword">var</span> v = <span class="hljs-keyword">new</span> Vector(<span class="hljs-number">-0.8</span>, <span class="hljs-number">1</span>).scale(<span class="hljs-number">2.2</span>)
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <p style="text-align: left">小球将会走圆形轨道：</p>
 <p style="text-align: left"><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi95MHJzSU5QcmxaelphSlJXQjZpYjU3SzhMUGtwbzZxejBaOEgyS051anFnOHdvOVZ6RElFaWNrbElSaWNpYWlhbUNvMmc2ZVVGNE1RMUZtRGlhcEs5dmw5b3BMUS82NDA?x-oss-process=image/format,png" title="null"></p>
 <p style="text-align: left">你可以随便调节参数，尝试其他情形，看看效果。</p>
 <p style="text-align: left">感谢你看到这里，希望有所帮助。</p>
 <p>- END-&nbsp;</p>
 <hr>
 <p style="text-align: center"><strong>文末彩蛋</strong></p>
 <p style="text-align: center">扫一扫二维码回复，"<strong>2020</strong>"，获得2020年最新前端、后端、大数据、人工智能、PHP等视频教程的百度云盘链接，获得之后，记得保存在自己的云盘里哦。</p>
 <pre class="has" name="code"><code class="language-php hljs">
</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre>
 <pre class="has" name="code"><code class="language-php hljs">编程·前端·社区</code><div class="hljs-button signin" data-title="登录后复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4334&quot;}" onclick="hljs.signin(event)"></div></pre> 
</div>
                </div>
